using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Services;
using MainGame.StateManagement;
using MainGame.SaveData;
using MainGame.GameSound;

namespace MainGame.GamePlay
{
  using PartialScores = MainGame.StateManagement.PartialScores;

  /// <summary>
  /// Gestiamo le regole di gioco, l'inizio della partita,
  /// le condizioni di fine partita e gli assegnamenti di punteggio
  /// </summary>
  public class GamePlayLayer : PlayerLayer
  {
    public GamePlayLayer(Game game)
      : base(game)
    {
      UpdateOrder = (int)ComponentOrders.LOGIC;
    }

    public override void Initialize()
    {
      Game.GetBoardRenderer().ReplaceCards += new ReplaceCards(BoardManager_ReplaceCards);

      base.Initialize();
    }

    protected override void OnTurnChanged()
    {
      if (blocks.Count == NUM_PLAYERS)
      {
        if (blocks.All(b => b.Cards.Any()))
        {
          var reference_color = blocks[0].Cards.First().Color;
          var loser = blocks.Where(b => b.Cards.First().Color == reference_color)
                            .MaxBy(b => b.Cards.First().Value);
          loser.PlayedBy.Score += NUM_PLAYERS;
          blocks.Clear();
        }
      }

      base.OnTurnChanged();
    }

    /// <summary>
    /// Estrae nuove carte dal mazzo e le assegna al giocatore
    /// </summary>
    /// <param name="player_index">L'indice del giocatore</param>
    /// <param name="card_index">La posizione in cui inserire la nuova carta</param>
    /// <returns>True se la carta e' stata rimpiazzata, false altrimenti</returns>
    bool BoardManager_ReplaceCards(int player_index, int card_index)
    {
      if (Game.GetContext().currentConfig.difficulty == GameDifficulty.Easy && extractedCards.Cards.Count > 0)
      {
        // Move card from the extracted cards to the player
        players[player_index].Cards.Insert(card_index, extractedCards.Cards.Last());
        extractedCards.Cards.Remove(extractedCards.Cards.Last());
        return true;
      }
      else
      {
        if (pack.Cards.Count() > 0)
        {
          // Move card from pack to the player
          players[player_index].Cards.Insert(card_index, pack.Cards.First());
          pack.Cards = pack.Cards.Skip(1).ToList();
          return true;
        }
      }
      return false;
    }

    protected override void BoardManager_ClickPlayerCard(int player_index, int card_index)
    {
      // Select/deselect card
      var b = players[player_index].Cards[card_index].Selected;
      players[player_index].Cards[card_index].Selected = !b;

      // Check if the player selected a valid combination
      CheckCombination(player_index);

      base.BoardManager_ClickPlayerCard(player_index, card_index);
    }

    protected override void BoardManager_ClickExtracted(int player_index)
    {
      // Select/deselect card
      if (extractedCards.Cards.Count() > 0)
      {
        var b = extractedCards.Cards.Last().Selected;
        extractedCards.Cards.Last().Selected = !b;
        // Play a sound
        var sound_manager = Game.Services.GetService(typeof(ISoundManager<GamePlaySounds>)) as ISoundManager<GamePlaySounds>;
        sound_manager.PlaySound((GamePlaySounds)random.Next((int)GamePlaySounds.CardFlip1, (int)GamePlaySounds.CardFlip5 + 1));

      }

      // Check if the player selected a valid combination
      CheckCombination(player_index);

      base.BoardManager_ClickExtracted(player_index);
    }

    /// <summary>
    /// Remove the cards with the given indices from the player's hand
    /// </summary>
    /// <param name="player_index">The index of the player</param>
    /// <param name="card_indices">The indices of the cards to remove</param>
    /// <param name="removeExtracted">Flag to indicate whether to remove a card from the extracted cards</param>
    protected void RemoveSelectedCards(int player_index, int[] card_indices, bool removeExtracted)
    {
      // Remove the first card (if selected) from the extracted cards
      if (removeExtracted)
        extractedCards.Cards.Remove(extractedCards.Cards.Last());
      // Remove the selected cards from the player's hand
      ++players[player_index].Score;
      for (int i = 0; i < card_indices.Count(); ++i)
      {
        players[player_index].Cards.RemoveAt(card_indices[i]);
        if (!BoardManager_ReplaceCards(player_index, card_indices[i]))
        {
          // If the card was not replaced, decrement indices of the next cards to remove
          for (int j = i; j < card_indices.Count(); ++j)
            card_indices[j]--;
        }
      }
      //for (int i = (card_indices.Count() - 1); i >= 0; --i)
      //{
      //  players[player_index].Cards.RemoveAt(card_indices[i]);
      //  BoardManager_ReplaceCards(player_index, card_indices[i]);
      //}
    }

    protected override void BoardManager_ReleasePlayerCard(int player_index, int card_index)
    {
      Card a, b;
      if (card_index > 0 && card_index < (players[player_index].Cards.Count() - 1))
      {
        a = players[player_index].Cards[card_index - 1];
        b = players[player_index].Cards[card_index + 1];
        //if ( players[player_index].CheckCards(a, b))
        //{
        //    ++players[player_index].Score;
        //    players[player_index].Cards[card_index].Selected = false;
        //    players[player_index].Cards.RemoveAt(card_index - 1);
        //}
      }

      // Condizione di Fine Partita (Vittoria)
      // Carte sul tavolo = 2 e Carte del Mazzo = 0
      //if (players[player_index].Cards.Count() == 2 && Pack.Cards.Count == 0)
      //{
      //  score.PlayerScores = (from p in players
      //                        select new PlayerScore()
      //                        {
      //                          Name = p.Name,
      //                          Score = p.Score
      //                        }).ToList();
      //  SavePartialScore(score);
      //  LevelWon(score);
      //}

      //// Condizione di Sconfitta-Stallo (Non ci sono mosse possibili)
      //if (players[player_index].MossePossibili() == 0 && Pack.Cards.Count == 0)
      //{
      //  LevelLost();
      //}

      

      base.BoardManager_ReleasePlayerCard(player_index, card_index);
    }

    protected void CheckCombination(int player_index)
    {
      // Extract selected cards
      List<Card> selectedCards = new List<Card>();
      bool isExtractedSelected = (extractedCards.Cards.Any() && extractedCards.Cards.Last().Selected);
      if (isExtractedSelected)
        selectedCards.Add(extractedCards.Cards.Last());
      selectedCards.AddRange(players[player_index].Cards.Where(c => c.Selected));

      // Collect the indices of the selected cards
      List<int> card_indices = new List<int>();
      for (int k = 0; k < players[player_index].Cards.Count(); ++k)
      {
        if (players[player_index].Cards[k].Selected)
          card_indices.Add(k);
      }

      // Case 1: two cards selected
      if (selectedCards.Count == 2)
      {
        if ((selectedCards.First().Value + selectedCards.Last().Value) == 11)
        {
          RemoveSelectedCards(player_index, card_indices.ToArray(), isExtractedSelected);          
        }
        else
        {
          // In case the selected cards are not J, Q or K
          // and have two different seeds, deselect them
          if (!selectedCards.All(c => c.Value > 10) || !selectedCards.All(c => c.Seed == selectedCards.First().Seed))
          { 
            players[player_index].Cards.ForEach(c => c.Selected = false);
            
            if (extractedCards.Cards.Count() > 0)
            extractedCards.Cards.Last().Selected = false;
          }
        }
      }
      // Case 2: three cards selected
      else if (selectedCards.Count == 3)
      {
        if (selectedCards.All(c => c.Value > 10) && selectedCards.All(c => c.Seed == selectedCards.First().Seed))
        {
          RemoveSelectedCards(player_index, card_indices.ToArray(), isExtractedSelected);
        }
        else
        { 
          players[player_index].Cards.ForEach(c => c.Selected = false);

          if (extractedCards.Cards.Count() > 0)
          extractedCards.Cards.Last().Selected = false;
        }
      }

      CheckVictory(player_index);
    }

    public void CheckVictory(int player_index)
    {
      // Condizioni di fine partita: Vittoria
      if (pack.Cards.Count() == 0 && extractedCards.Cards.Count() == 0 && players[player_index].Cards.Count() == 0)
        LevelWon(score);
      else
      {
        // Condizioni di fine partita: Sconfitta
        if (pack.Cards.Count() == 0 && players[player_index].Cards.Count() > 0)
        {
          bool LostCondition1 = true, LostCondition2 = true, LostCondition3 = true;

          // Non esistono carte (in cards e extractedCards) la cui somma faccia 11
          for (int i = 0; i < players[player_index].Cards.Count; i++)
          {
            for (int j = 0; j < players[player_index].Cards.Count; j++)
            {
              if (i != j)
                if (players[player_index].Cards[i].Value + players[player_index].Cards[j].Value == 11) { LostCondition1 = false; }

            }

            if (extractedCards.Cards.Count() > 0)
              if (players[player_index].Cards[i].Value + extractedCards.Cards.Last().Value == 11) { LostCondition2 = false; }
          }

          // Non esistono J Q K dello stesso seme
          for (int s = 0; s < 4; s++)
          {
            CardSeed cs = CardSeed.Hearts;

            //if(s==0) cs = CardSeed.Hearts;
            if (s == 1) cs = CardSeed.Diamonds;
            if (s == 2) cs = CardSeed.Clubs;
            if (s == 3) cs = CardSeed.Spades;

            if (Exist(players[player_index].Cards, 11, cs) && Exist(players[player_index].Cards, 12, cs) && Exist(players[player_index].Cards, 13, cs)) LostCondition3 = false;

          }


          if (LostCondition1 && LostCondition2 && LostCondition3)
            LevelLost();
        }
      }
    }

    public bool Exist(List<Card> cardsList, int cardValue, CardSeed cardSeed) 
    {
      if(cardsList.Count() > 0)
      for (int z = 0; z < cardsList.Count(); z++)
      {
        if (cardsList[z].Value == cardValue && cardsList[z].Seed == cardSeed) return true;
      }

      if (extractedCards.Cards.Count() > 0)
      if (extractedCards.Cards.Last().Value == cardValue && extractedCards.Cards.Last().Seed == cardSeed) return true;

      return false;
    }

    protected override void StartGame()
    {
      var range = (from i in Enumerable.Range(1, 13) select i);
      Func<CardColor, IEnumerable<Card>> colors = c =>
        from i in range select new Card() { Color = c, Value = i, Selected = false };

      // Crea un nuovo mazzo
      pack = new GamePlay.Pack()
      {
        Cards = colors(CardColor.Blue).Concat(
             colors(CardColor.Green)).Concat(
             colors(CardColor.Red)).Concat(
             colors(CardColor.White)).ToList()
      };

      // Definisce tipo di Mazzo (Semi delle Carte)
      for (int i = 0; i < pack.Cards.Count; i++)
      {
        if (pack.Cards.ElementAt(i).Color == CardColor.Red) { pack.Cards.ElementAt(i).Seed = CardSeed.Hearts; }
        if (pack.Cards.ElementAt(i).Color == CardColor.Green) { pack.Cards.ElementAt(i).Seed = CardSeed.Diamonds; }
        if (pack.Cards.ElementAt(i).Color == CardColor.White) { pack.Cards.ElementAt(i).Seed = CardSeed.Clubs; }
        if (pack.Cards.ElementAt(i).Color == CardColor.Blue) { pack.Cards.ElementAt(i).Seed = CardSeed.Spades; }
      }

      // Mischia il mazzo
      var r = new Random();
      for (int i = 0; i < pack.Cards.Count / 2; i++)
      {
        var ai = r.Next(pack.Cards.Count);
        var bi = r.Next(pack.Cards.Count);

        var a = pack.Cards[ai];
        var b = pack.Cards[bi];

        pack.Cards[bi] = a;
        pack.Cards[ai] = b;
      }

      // Crea il mucchietto di carte estratte (vuoto)
      extractedCards = new ExtractedCards()
      {
        Cards = new List<Card>()
      };

      // Crea i giocatori
      for (int p = 0; p < NUM_PLAYERS; p++)
        players.Add(new Player()
        {
          Score = 0,
          Hand = new PlayerBlock() { Cards = new List<Card>() },
          IsHuman = false,
          Name = Locale.Strings.Player + " " + p.ToString() //"Player " + p.ToString()
        });
#warning PER ATTIVARE UN GIOCATORE UMANO, VEDERE QUI
      players[0].IsHuman = true;
      players[0].Name = Game.GetContext().currentProfile.username;

      // Assegna le carte
      for (int i = 0; i < STARTING_CARDS; i++)
      {
        for (int p = 0; p < NUM_PLAYERS; p++)
        {
          pick_player(p);
        }
      }

      //CurrentPlayerIndex = r.Next(NUM_PLAYERS);
      CurrentPlayerIndex = 0;
    }

    protected override void LoadGame(SavedGame savedGame)
    {
      // Carica il mazzo
      pack = savedGame.pack;

      // Carica i giocatori - solitario: 1 giocatore
      players.Add(new Player()
      {
        Score = 0,
        Hand = savedGame.playerCards,
        IsHuman = true,
        Name = Game.GetContext().currentProfile.username
      });

      // Carica le carte estratte
      extractedCards = savedGame.extractedCards;

      CurrentPlayerIndex = 0;
    }

    /// <summary>
    /// Assegna una carta a un giocatore
    /// </summary>
    /// <param name="index">L'indice del giocatore nell'array dei giocatori</param>
    void pick_player(int index)
    {
      if (pack.Cards.Count > 0)
      {
        players[index].Cards.Add(pack.Cards.First());
        pack.Cards = pack.Cards.Skip(1).ToList();
      }
    }

    /// <summary>
    /// Salva il punteggio di tutti i giocatori per il round corrente
    /// </summary>
    /// <param name="score">I punteggi da salvare</param>
    protected void SavePartialScore(Score score)
    {
      if (PartialScores.scoreList == null)
        PartialScores.scoreList = new List<Score>();
      PartialScores.scoreList.Add(score);
    }
  }
}
